/*//. QueryXMMS (qxmms) Network Daemon.

This is my first attempt at doing something with sockets that is actually useful. It allows
for remote sessions to operate a XMMS somewhere else, with as simple a client as telnet.

*/

#include "qxmms.h"

#define SERVER_VERSION "1.0"
#define SERVER_DATE "20060902"

#define SERVER_PORT 4264
#define SERVER_MAX_CONNECTIONS 24
#define SERVER_CONNECT_QUEUE 12
#define SERVER_RECV_MAX_LENGTH 1024

typedef struct client_info {
	int active:1;
	int socket;
} client_info_s;

#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/time.h>
#include <sys/types.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>

struct sockaddr_in server_connection_info;
struct client_info client_connection_info[SERVER_MAX_CONNECTIONS];
struct sockaddr_in client_temp;

socklen_t sizeof_sockaddr_in = sizeof(struct sockaddr_in);

fd_set socket_set;
fd_set socket_key;

int main(int argc, char **args) {

	if(argc > 1) {
		if(!strcmp(args[1],"--version")) {
			printf("QueryXMMS Remote qxmmsd v%s (%s)\n",SERVER_VERSION,SERVER_DATE);
			return 0;
		}
	}

	int a,b,c;

	int socket_tracker; //. highest numbered socket gah.
	int socket_server; //. server socket.
	int socket_temp; //. temp incoming socket.
	
	int i_size;
	int o_size;
	
	char i_buffer[SERVER_RECV_MAX_LENGTH];
	char o_buffer[SERVER_RECV_MAX_LENGTH];
	
	char *cmd[24];
	
	struct sockaddr_in *server;
	struct client_info *client;
	
	//. catch stdio. why? well when qxmms sends back xmms info i want to catch it
	//. and then send it back to the client that asked for it.
	setvbuf(stdout,o_buffer,_IOLBF,SERVER_RECV_MAX_LENGTH);


	server = &server_connection_info;
	client = client_connection_info;
		
	socket_server = socket(PF_INET,SOCK_STREAM,0);
	setsockopt(socket_server, SOL_SOCKET, SO_REUSEADDR, (int *)TRUE, sizeof(int));
	
	//. server setup.
	server->sin_family = AF_INET;
	server->sin_port = htons(SERVER_PORT);
	server->sin_addr.s_addr = INADDR_ANY;
	memset(server->sin_zero,'\0',8);
	
	//. initialize client array.
	for(a = 0; a < SERVER_MAX_CONNECTIONS; a++) {
		client[a].active = FALSE;
	}

	//. setup the socket monitor.
	FD_ZERO(&socket_set);
	FD_SET(socket_server,&socket_key);
	socket_tracker = socket_server;
	
	while(bind(socket_server, (struct sockaddr *)server, sizeof(struct sockaddr)) < 0) {
		printf("error: bind(%d)\n",errno);
		sleep(1);
	}
	
	if(listen(socket_server, SERVER_CONNECT_QUEUE)) {
		puts("listen() error.");
		exit(1);
	}
	
	printf("%% listening port %d.\n",SERVER_PORT);
	
	while(1) {

		socket_set = socket_key;
		
		if(select((socket_tracker + 1),&socket_set,NULL,NULL,NULL) != -1) {
			if(FD_ISSET(socket_server, &socket_set)) {
			
				//. incoming connection.
				socket_temp = accept(socket_server, (struct sockaddr *)&client_temp, (socklen_t *)&sizeof_sockaddr_in);
			
				//. assign.
				for(a = 0; a < SERVER_MAX_CONNECTIONS; a++) {
					if(client[a].active == 0) {
						break;
					}
				}
				
				client[a].active = TRUE;
				client[a].socket = socket_temp;

				FD_SET(client[a].socket, &socket_key);
				if(socket_temp > socket_tracker) {
					socket_tracker = socket_temp;
				}
				
				printf("%% connection established: id %d.\n",a);
				
			} else {
			
				//. incoming data.
				for(a = 0; a < SERVER_MAX_CONNECTIONS; a++) {
					if(FD_ISSET(client[a].socket, &socket_set)) {
						i_size = recv(client[a].socket,i_buffer,SERVER_RECV_MAX_LENGTH,0);
						
						//. this was a disconnection.
						if(i_size == 0) {
							client[a].active = FALSE;
							FD_CLR(client[a].socket,&socket_key);							
							printf("connection id %d closed.\n",a);
						} else {
						
							//. strip telnet stuffs.
							if(i_buffer[i_size - 1] == '\n') {			
								i_buffer[i_size - 1] = '\0';
							}
							if(i_buffer[i_size - 2] == '\r') {			
								i_buffer[i_size - 2] = '\0';
							}

							printf("%% data recv id %d: %s\n",a,i_buffer);

							//. build the pointer of pointers (argv style ftw lol)
							c = 1;
							cmd[c++] = &i_buffer[0];
							for(b = 0; b < i_size; b++) {
								if(i_buffer[b] == ' ') {
									i_buffer[b] = '\0';
									cmd[c++] = &i_buffer[b + 1];
								} else if(i_buffer[b] == '\0') {
									break;
								}
							}
							
							memset(&o_buffer,'\0',SERVER_RECV_MAX_LENGTH);
							
							//. perform command.
							qxmms_process(c, cmd);
							
							//. and send back qxmms output to the client. \o/
							send(client[a].socket,o_buffer,strlen(o_buffer),0);
	
						}
						
						
					}					
				} //. END SOCKET CHECK LOOP.			
			
			} //. END IF NEW CONNECT/OLD CONNECT.
			
			memset(&i_buffer,'\0',SERVER_RECV_MAX_LENGTH);
			memset(&o_buffer,'\0',SERVER_RECV_MAX_LENGTH);
	
			
		} //. END OF SELECT.

	} //.  END OF WHILE.

}
